package svgedit.gui;

import gnu.jpdf.PDFJob;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import javaUncompress.JavaUncompress;
import javacompress.CompressingFile;
import javacompress.CompressingFileSVGZ;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import language.English;
import language.German;
import language.Japanese;
import language.LanguageLoader;
import svgedit.gui.actions.DeleteAction;
import svgedit.gui.actions.EditDocumentPropertiesAction;
import svgedit.gui.actions.ExportPDFAction;
import svgedit.gui.actions.ExportPNGAction;
import svgedit.gui.actions.GroupAction;
import svgedit.gui.actions.InsertCircleElementAction;
import svgedit.gui.actions.InsertLineElementAction;
import svgedit.gui.actions.InsertRectElementAction;
import svgedit.gui.actions.NewDocumentAction;
import svgedit.gui.actions.OpenDocumentAction;
import svgedit.gui.actions.QuitAction;
import svgedit.gui.actions.RedoAction;
import svgedit.gui.actions.SaveDocumentAction;
import svgedit.gui.actions.SaveDocumentAsAction;
import svgedit.gui.actions.SelectAllAction;
import svgedit.gui.actions.SveAsSVGZAction;
import svgedit.gui.actions.UndoAction;
import svgedit.gui.actions.UngroupAction;
import svgedit.svg.SVGDocument;
import svgedit.svg.SVGElement;
import svgedit.svg.SVGGroup;
import svgedit.svg.SVGLength;
import svgedit.svg.SVGPaint;
import svgedit.svg.SVGStylable;
import svgedit.xml.XMLReader;
import svgedit.xml.XMLWriter;
import svgundoredo.AbstractCommand;
import svgundoredo.CommandStack;

/** Displays a window containing a SVG view, toolbar and menu. */
@SuppressWarnings("serial")
public class Frame extends JFrame
{
    
    /** User preferences */
    private Preferences preferences;
    /** The document displayed by the frame */
    private SVGDocument document;
    
    /** An SVG view of the current document */
    private View view;
    
    /** Undo Redo */
    private static CommandStack undoredo;
    // Actions available from the menu and toolbar */
    private Action newAction;
    private Action openAction;
    private Action saveAction;
    private Action saveAsAction;
    private Action documentPropertiesAction;
    private Action japanese;
    private Action english;
    private Action german;
    private Action quitAction;
    
    private Action exportPNG;
    private Action exportPDF;
    private Action sveAsSVGZ;
    
    // Actions for modifying the current selection */
    private static Action undoAction;
    private static Action redoAction;
    private Action selectAllAction;
    private Action groupAction;
    private Action ungroupAction;
    private Action deleteAction;
    
    // Actions for adding new elements to the document */
    private Action insertRectAction;
    private Action insertCircleAction;
    private Action insertLineAction;
    private JToggleButton[] toolBarButtons;
    
    private PaintDropDown fillColorPicker;
    private PaintDropDown strokeColorPicker;
    private JTextField strokeWidthTextField;
    private JMenu fileMenu;
    private JMenu editMenu;
    private JMenu insertMenu;
    private JMenu language;
    private JavaUncompress javaUncompress;
    private CompressingFileSVGZ compressingFileSVGZ;
    private CompressingFile compressingFile;
    
    private class FrameWindowListener extends WindowAdapter
    {
        
        /** Invokes quit action when user attempts to close window */
        @Override
        public void windowClosing(WindowEvent we)
        {
            quitAction.actionPerformed(null);
        }
    }
    
    /** Creates a frame with a new, empty document. */
    public Frame()
    {
        preferences = new Preferences();
        // Create all actions presented by the menu bar and toolbar
        undoredo = new CommandStack();
        newAction = new NewDocumentAction(this);
        openAction = new OpenDocumentAction(this);
        saveAction = new SaveDocumentAction(this);
        saveAsAction = new SaveDocumentAsAction(this);
        exportPNG = new ExportPNGAction(this);
        exportPDF = new ExportPDFAction(this);
        sveAsSVGZ = new SveAsSVGZAction(this);
        documentPropertiesAction = new EditDocumentPropertiesAction(this);
        japanese = new Japanese(this);
        english = new English(this);
        german = new German(this);
        quitAction = new QuitAction(this);
        
        undoAction = new UndoAction(this);
        undoAction.setEnabled(false);
        redoAction = new RedoAction(this);
        redoAction.setEnabled(false);
        selectAllAction = new SelectAllAction(this);
        groupAction = new GroupAction(this);
        ungroupAction = new UngroupAction(this);
        deleteAction = new DeleteAction(this);
        
        insertRectAction = new InsertRectElementAction(this);
        insertCircleAction = new InsertCircleElementAction(this);
        insertLineAction = new InsertLineElementAction(this);
        
        // Associate keyboard shortcuts with actions
        
        newAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                .getKeyStroke(KeyEvent.VK_N, Toolkit.getDefaultToolkit()
                        .getMenuShortcutKeyMask()));
        openAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                .getKeyStroke(KeyEvent.VK_O, Toolkit.getDefaultToolkit()
                        .getMenuShortcutKeyMask()));
        saveAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                .getKeyStroke(KeyEvent.VK_S, Toolkit.getDefaultToolkit()
                        .getMenuShortcutKeyMask()));
        saveAsAction
                .putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                        .getKeyStroke(KeyEvent.VK_S, KeyEvent.SHIFT_MASK
                                | Toolkit.getDefaultToolkit()
                                        .getMenuShortcutKeyMask()));
        
        selectAllAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                .getKeyStroke(KeyEvent.VK_A, Toolkit.getDefaultToolkit()
                        .getMenuShortcutKeyMask()));
        
        undoAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                .getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit()
                        .getMenuShortcutKeyMask()));
        redoAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                .getKeyStroke(KeyEvent.VK_Y, Toolkit.getDefaultToolkit()
                        .getMenuShortcutKeyMask()));
        groupAction.putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                .getKeyStroke(KeyEvent.VK_G, Toolkit.getDefaultToolkit()
                        .getMenuShortcutKeyMask()));
        ungroupAction
                .putValue(AbstractAction.ACCELERATOR_KEY, KeyStroke
                        .getKeyStroke(KeyEvent.VK_G, KeyEvent.SHIFT_MASK
                                | Toolkit.getDefaultToolkit()
                                        .getMenuShortcutKeyMask()));
        
        // Hook up handler to window close event to display save confirmation
        // dialog.
        
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        addWindowListener(new FrameWindowListener());
        
        // Create menus
        
        JMenuBar menuBar = new JMenuBar();
        add(menuBar, BorderLayout.NORTH);
        
        fileMenu = new JMenu(LanguageLoader.file);
        fileMenu.add(new JMenuItem(newAction));
        fileMenu.add(new JMenuItem(openAction));
        fileMenu.add(new JMenuItem(saveAction));
        fileMenu.add(new JMenuItem(saveAsAction));
        fileMenu.add(new JMenuItem(sveAsSVGZ));
        fileMenu.add(new JMenuItem(documentPropertiesAction));
        fileMenu.add(new JMenuItem(exportPNG));
        fileMenu.add(new JMenuItem(exportPDF));
        
        language = new JMenu("Language");
        language.add(new JMenuItem(japanese));
        language.add(new JMenuItem(english));
        language.add(new JMenuItem(german));
        fileMenu.add(language);
        fileMenu.add(new JMenuItem(quitAction));
        menuBar.add(fileMenu);
        
        editMenu = new JMenu(LanguageLoader.edit);
        editMenu.add(new JMenuItem(undoAction));
        editMenu.add(new JMenuItem(redoAction));
        editMenu.add(new JMenuItem(selectAllAction));
        editMenu.add(new JMenuItem(groupAction));
        editMenu.add(new JMenuItem(ungroupAction));
        editMenu.add(new JMenuItem(deleteAction));
        menuBar.add(editMenu);
        
        insertMenu = new JMenu(LanguageLoader.insert);
        insertMenu.add(new JMenuItem(insertRectAction));
        insertMenu.add(new JMenuItem(insertCircleAction));
        insertMenu.add(new JMenuItem(insertLineAction));
        menuBar.add(insertMenu);
        
        // Create toolbar
        
        fillColorPicker = new PaintDropDown(PaintDropDown.PAINT_ATTRIBUTE_FILL);
        fillColorPicker.addItemListener(new ItemListener()
        {
            public void itemStateChanged(ItemEvent ie)
            {
                SVGPaint paint = fillColorPicker.getPaint();
                view.setSelectedFill(paint);
                view.getDefaultStyle().getFill().setValueFromPaint(paint);
            }
        });
        
        strokeColorPicker = new PaintDropDown(
                PaintDropDown.PAINT_ATTRIBUTE_STROKE);
        strokeColorPicker.addItemListener(new ItemListener()
        {
            public void itemStateChanged(ItemEvent ie)
            {
                SVGPaint paint = strokeColorPicker.getPaint();
                view.setSelectedStroke(paint);
                view.getDefaultStyle().getStroke().setValueFromPaint(paint);
            }
        });
        
        strokeWidthTextField = new JTextField();
        strokeWidthTextField.getDocument().addDocumentListener(
                new DocumentChangedAdapter()
                {
                    
                    @Override
                    public void documentChanged()
                    {
                        try
                        {
                            SVGLength length = new SVGLength();
                            length.setValueFromString(strokeWidthTextField
                                    .getText());
                            view.setSelectedStrokeWidth(length);
                            view.getDefaultStyle().getStrokeWidth()
                                    .setValueFromLength(length);
                        }
                        catch (NumberFormatException e)
                        {
                            // Ignore parse error; the user may still be typing
                        }
                    }
                    
                });
        strokeWidthTextField.addFocusListener(new FocusAdapter()
        {
            
            @Override
            public void focusLost(FocusEvent fe)
            {
                // If invalid text was left in the text field, restore the
                // text to the current selection
                view.setSelectedElements(view.getSelectedElements());
            }
            
        });
        
        toolBarButtons = new JToggleButton[] {
                new JToggleButton(insertRectAction),
                new JToggleButton(insertCircleAction),
                new JToggleButton(insertLineAction) };
        
        JToolBar toolBar = new JToolBar();
        toolBar.add(newAction);
        toolBar.add(openAction);
        toolBar.add(saveAction);
        toolBar.add(exportPNG);
        toolBar.add(exportPDF);
        toolBar.add(new JToolBar.Separator());
        for (JToggleButton button : toolBarButtons)
            toolBar.add(button);
        toolBar.add(new JToolBar.Separator());
        toolBar.add(new JLabel("Fill:"));
        toolBar.add(fillColorPicker);
        toolBar.add(new JLabel("Stroke:"));
        toolBar.add(strokeColorPicker);
        toolBar.add(new JLabel("Stroke Width:"));
        toolBar.add(strokeWidthTextField);
        
        JPanel viewContainer = new JPanel();
        viewContainer.setLayout(new BorderLayout());
        add(viewContainer);
        
        viewContainer.add(toolBar, BorderLayout.NORTH);
        
        // Create view
        
        view = new View();
        view.addViewListener(new ViewListener()
        {
            public void viewSelectionChanged(View view)
            {
                selectionChanged();
            }
            
            public void viewManipulatorChanged(View view)
            {
                setManipulatorAction(null);
            }
        });
        viewContainer.add(view);
        
        // Map delete and backspace keys to delete action
        view.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0),
                "delete");
        view.getInputMap().put(
                KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), "delete");
        view.getActionMap().put("delete", deleteAction);
        
        // Always start with blank canvas
        newFile();
        
        view.setSelectionManipulator();
        
        // Set an initial view size in case the document size is based on the
        // view
        view.setSize(new Dimension(480, 360));
        
        // Size the view to the document
        view.setPreferredSize(new Dimension((int) document.getWidth()
                .getValue() + 20, (int) document.getHeight().getValue() + 20));
        pack();
        
    }
    
    /**
     * Gets the preferences object to use for getting and setting user defaults.
     * 
     * @return the preferences object associated with this frame
     */
    public Preferences getPreferences()
    {
        return preferences;
    }
    
    /**
     * Gets the document currently displayed in the frame.
     * 
     * @return an SVG document
     */
    public SVGDocument getDocument()
    {
        return document;
    }
    
    /**
     * Sets the document currently displayed in the frame.
     * 
     * @param document
     *            the document to display
     * @param file
     *            the file the document was loaded from, or null if it's a new
     *            file
     */
    private void setDocument(SVGDocument document, File file)
    {
        this.document = document;
        view.setDocument(document);
        setFile(file);
    }
    
    /** Gets the view presenting the document for this frame. */
    public View getView()
    {
        return view;
    }
    
    /**
     * Sets the action responsible for the view's current manipulator. This
     * causes that action's toolbar button to appear selected, and for all other
     * toolbar buttons to unselect.
     * 
     * @param action
     *            the action that set the view's manipulator
     */
    public final void setManipulatorAction(Action action)
    {
        for (JToggleButton button : toolBarButtons)
        {
            button.setSelected(button.getAction() == action);
        }
    }
    
    /**
     * Set the filename of the current document. Sets the frame's title and
     * enables the appropriate save menu items.
     * 
     * @param file
     *            the filename of the current document, or null if the document
     *            is new and has no filename
     */
    private void setFile(File file)
    {
        document.setFile(file);
        
        if (file != null)
            setTitle(file.getName() + " - SVGEdit");
        else
            setTitle("SVGEdit");
    }
    
    /** Sets the document to a new document with no associated file. */
    public final void newFile()
    {
        SVGDocument doc = new SVGDocument(view);
        setDocument(doc, null);
    }
    
    /**
     * Loads a file and sets it as the current document. Displays an error
     * message and leaves the current document unchanged if the document cannot
     * be opened.
     * 
     * @param file
     *            the file to load
     */
    public void openFile(File file)
    {
        
        String path = file.getPath();
        if (path.toLowerCase().contains(".svg.gz")
                && !path.toLowerCase().endsWith(".svg"))
        {
            System.out.println(file.getPath().toLowerCase());
            javaUncompress = new JavaUncompress(file.getPath().toLowerCase());
            file = new File(javaUncompress.getOutFilename());
        }
        else if (path.toLowerCase().contains(".svgz")
                && !path.toLowerCase().endsWith(".svg"))
        {
            
            System.out.println(file.getPath().toLowerCase());
            javaUncompress = new JavaUncompress(file.getPath().toLowerCase());
            file = new File(javaUncompress.getOutFilename());
            
        }
        
        try
        {
            SVGDocument doc = XMLReader.read(file, view);
            setDocument(doc, file);
        }
        catch (IOException e)
        {
            JOptionPane.showMessageDialog(getRootPane(), e.getMessage(),
                    "Unable to open file", JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * Writes the current document to the given file.
     * 
     * @param file
     *            the file to save to
     */
    
    // public void saveFile(File file) {
    // assert document != null;
    // assert file != null;
    //
    // try {
    // XMLWriter.write(document, file);
    // setFile(file);
    // document.setModified(false);
    // } catch (IOException e) {
    // JOptionPane.showMessageDialog(getRootPane(), e.getMessage(),
    // "Unable to save file", JOptionPane.ERROR_MESSAGE);
    // }
    // }
    
    public void saveFile(File file)
    {
        assert document != null;
        assert file != null;
        String path = file.getPath();
        File ooo = new File(file.getPath().toLowerCase().toString());
        
        try
        {
            XMLWriter.write(document, file);
            setFile(file);
            document.setModified(false);
            
            if (path.toLowerCase().contains(".svg.gz"))
            {
                this.compressingFile = new CompressingFile(file.getPath()
                        .toLowerCase().toString());
                compressingFile.doCompressFile(file.getPath().toLowerCase()
                        .toString());
                this.saveAsAction.getValue(getName());
                System.out.println(this.saveAsAction.getValue(getName()));
                System.out.println(file.getPath().toLowerCase().toString());
                boolean success = file.delete();
                if (!success)
                {
                    System.out.println("Deletion failed.");
                    
                }
                else
                {
                    System.out.println("File deleted.");
                }
                
            }
            else if (path.toLowerCase().contains(".svgz"))
            {
                this.compressingFileSVGZ = new CompressingFileSVGZ(file
                        .getPath().toLowerCase().toString());
                compressingFileSVGZ.doCompressFile(file.getPath().toLowerCase()
                        .toString());
                this.saveAsAction.getValue(getName());
                
                // File f1 = new
                // File(file.getPath().toLowerCase().toString()+"svg.gz");
                boolean success = file.delete();
                if (!success)
                {
                    System.out.println("Deletion failed.");
                    
                }
                else
                {
                    System.out.println("File deleted.");
                }
                
            }
            
        }
        catch (IOException e)
        {
            JOptionPane.showMessageDialog(getRootPane(), e.getMessage(),
                    "Unable to save file", JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * Writes the current document to the given PNG file.
     * 
     * @param file
     *            file to save to
     */
    public void exportPNG(File file)
    {
        assert document != null;
        assert file != null;
        File outputFile = new File(file.getPath());
        int width = (int) document.getWidth().getValue();
        int height = (int) document.getHeight().getValue();
        BufferedImage bufferedImage = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
        Graphics g = bufferedImage.getGraphics();
        view.paint(g);
        
        try
        {
            ImageIO.write(bufferedImage, "png", outputFile);
            setFile(file);
            document.setModified(false);
        }
        catch (IOException e)
        {
            JOptionPane.showMessageDialog(getRootPane(), e.getMessage(),
                    "Unable to save PNG file", JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * Writes the current document to the given PDF file.
     * 
     * @param file
     *            the file to save to
     */
    public void exportPDF(File file)
    {
        assert document != null;
        assert file != null;
        
        try
        {
            FileOutputStream outputFile = new FileOutputStream(file.getPath());
            PDFJob job = new PDFJob(outputFile);
            Graphics g = job.getGraphics();
            view.paint(g);
            g.dispose();
            job.end();
        }
        catch (IOException e)
        {
            JOptionPane.showMessageDialog(getRootPane(), e.getMessage(),
                    "Unable to save PDF file", JOptionPane.ERROR_MESSAGE);
        }
    }
    
    /**
     * If the document has unsaved modifications, prompt the user to save
     * changes.
     * 
     * @return false if the user pressed 'Cancel' at the dialog, otherwise true
     */
    public boolean confirmSaveChanges()
    {
        if (document.getModified())
        {
            int result = JOptionPane.showConfirmDialog(null,
                    "Save changes to current document?", "Save Changes",
                    JOptionPane.YES_NO_CANCEL_OPTION,
                    JOptionPane.WARNING_MESSAGE);
            
            if (result == JOptionPane.CANCEL_OPTION)
                return false;
            
            if (result == JOptionPane.OK_OPTION)
            {
                if (document.getFile() != null)
                    saveAction.actionPerformed(null);
                else
                    saveAsAction.actionPerformed(null);
            }
        }
        
        return true;
    }
    
    /**
     * Handler called when the view's selection changes. Updates the toolbar
     * elements to display the properties of the current selection, and enables
     * relevant actions for the current selection.
     */
    private void selectionChanged()
    {
        SVGElement[] selectedElements = view.getSelectedElements();
        
        SVGStylable style;
        if (selectedElements.length != 0)
            style = view.getSelectionStyle();
        else
            style = view.getDefaultStyle();
        
        fillColorPicker.setPaint(style.getFill());
        strokeColorPicker.setPaint(style.getStroke());
        if (style.getStrokeWidth() != null)
            strokeWidthTextField
                    .setText(style.getStrokeWidth().valueAsString());
        else
            strokeWidthTextField.setText("");
        
        // Enable delete action if elements are selected
        deleteAction.setEnabled(selectedElements.length > 0);
        
        // Enable group action if more than one element is selected
        groupAction.setEnabled(selectedElements.length > 1);
        
        // Enable ungroup action if a group is selected
        ungroupAction.setEnabled(false);
        for (SVGElement elem : selectedElements)
        {
            if (elem instanceof SVGGroup)
                ungroupAction.setEnabled(true);
        }
    }
    
    public void setLanguage()
    {
        String key = "Name";
        fileMenu.setText(LanguageLoader.file);
        editMenu.setText(LanguageLoader.edit);
        insertMenu.setText(LanguageLoader.insert);
        language.setText(LanguageLoader.lang);
        
        newAction.putValue(key, LanguageLoader._new);
        openAction.putValue(key, LanguageLoader.open);
        saveAction.putValue(key, LanguageLoader.save);
        saveAsAction.putValue(key, LanguageLoader.saveas);
        documentPropertiesAction.putValue(key, LanguageLoader.properties);
        quitAction.putValue(key, LanguageLoader.quit);
        exportPNG.putValue(key, LanguageLoader.exportPNG);
        exportPDF.putValue(key, LanguageLoader.exportPDF);
        
        undoAction.putValue(key, LanguageLoader.undo);
        redoAction.putValue(key, LanguageLoader.redo);
        selectAllAction.putValue(key, LanguageLoader.selectall);
        groupAction.putValue(key, LanguageLoader.group);
        ungroupAction.putValue(key, LanguageLoader.ungroup);
        deleteAction.putValue(key, LanguageLoader.delete);
        
        insertRectAction.putValue(key, LanguageLoader.rect);
        insertCircleAction.putValue(key, LanguageLoader.circle);
        insertLineAction.putValue(key, LanguageLoader.line);
        
    }
    
    public static void setUndoRedoVisible()
    {
        undoAction.setEnabled(undoredo.checkUndo());
        redoAction.setEnabled(undoredo.checkRedo());
    }
    
    public CommandStack getUndoRedoStack()
    {
        return undoredo;
    }
    
    public static void addCommandToStack(AbstractCommand command)
    {
        undoredo.addToStack(command);
        setUndoRedoVisible();
    }
}
